const fs = require('fs');
const path = require('path');
const HtmlTpl = require("./htmltpl");
const chalk = require("chalk");
const JKHTMLParser = require("./JKHTMLParser");
const mime = require("mime");
const sass = require('node-sass')
const iconv = require('iconv-lite');

const file_content = file => {
    if(!fs.existsSync(file)) {
        console.log(chalk.yellow(`!警告: 文件 ${file} 不存在`));
        return "";
    }
    
    return fs.readFileSync(file).toString()
};

const project_file_content = file => file_content(path.dirname(__dirname) + '/' + file);
const hljs = require('highlight.js');

let hasMermaid = false;

const markdown = require('markdown-it')({
    html: true,
    xhtmlOut: true,
    highlight: function (str, lang) {
        let tpl = file_content(__dirname + '/tpl/frame-code-highlight.html');
        
        if(lang == 'mermaid') {
            hasMermaid = true;
            return "<div class='mermaid'>"+ str +"</div>";
        }
        
        if (lang && hljs.getLanguage(lang)) {
            try {
                return new HtmlTpl(tpl).compile({
                    content: hljs.highlight(lang, str, true).value,
                    lang,
                });
            } catch (__) {}
        }

        return new HtmlTpl(tpl).compile({
            content: markdown.utils.escapeHtml(str),
            lang,
        });
    }
});

module.exports = class Compiler {
    constructor() {
        //源文件路径
        this.sourcePath = null;
        //打包收集的资源内容
        this.resources = {};
        //保存路径
        this.saveTarget = null;
        //扫描出来的文件列表(待编译文件)
        this.fileList = null;
        //额外的css样式文件路径
        this.cssfile = null;
        //额外的js脚本文件路径
        this.jsfile = null;
        //语法高亮风格
        this.histyle = null;
        //是否保留文件名前面的数字序号后缀
        //md文件可以通过数字序号定义文件顺序, 例如 1.xxx.md, 2.bbb.md
        this.reserveNumPrefix = false;
        //可编译的文件后缀
        this.allowSubfix = ['.md'];
        //源文件路径是否为目录
        this.sourceIsDir = true;
    }

    /**
     * 获取语法高亮样式
     * @returns {string}
     */
    getHiStyleContent() {
        let histyleFile = __dirname + '/../node_modules/highlight.js/styles/' + this.histyle + '.css';
        let defaultFile = __dirname + '/../node_modules/highlight.js/styles/agate.css';
        // let defaultFile = __dirname + '/../node_modules/highlight.js/styles/default.css';
        
        if(this.histyle && fs.existsSync(histyleFile)) {
           return file_content(histyleFile);
        }
        
        return file_content(defaultFile);
    }

    /**
     * 获取保存编译结果的位置
     * @returns {string|null}
     */
    getSaveTarget() {
        if(this.saveTarget) {
            return this.saveTarget;
        } else {
            if(this.sourceIsDir) {
                return path.resolve(this.sourcePath) + '.html';
            } else {
                return this.sourcePath + '.html';
            }
        }
    }
    
    getFileSortInfo() {
        if(fs.statSync(this.sourcePath).isDirectory()) {
            let content = file_content(this.sourcePath + '/sort');
            
            if(!content) {
                return null;
            }
            
            return content.replace(/\r/g, '\n').replace(/\n+/g, '\n').split('\n').map(
                row => row.replace('\\', '/')
                    .replace("/", '\\/')
                    .replace('*', '.*')
                    .replace('?', '.')
            );
        }
        
        return null;
    }
    
    reSort(list) {
        let sortInfo = this.getFileSortInfo();
        
        if(!sortInfo) {
            return list;
        }
     
        for(let item of list) {
            for(let i in sortInfo) {
                let sortRule = sortInfo[i];
                sortRule = new RegExp('^'+sortRule+'$');
                
                if(sortRule.test(item.name.replace(/^\/+/, ''))) {
                    item.sortIndex = parseInt(i)+1;
                    break;
                }
            }
        }
        
        list.sort((a,b) => a.sortIndex - b.sortIndex);
        return list;
    }

    /**
     * 扫描待编译文件
     * @returns {null}
     */
    scanFile() {
        if (null !== this.fileList) {
            return this.fileList
        }

        let fileList = [];
        let dirList = [];
        
        if(!this.sourcePath) {
            throw Error('source 为空');
        }

        if (!fs.statSync(this.sourcePath).isDirectory()) {
            fileList.push({absolute:this.sourcePath, name:this.sourcePath});
            this.sourceIsDir = false;
            return this.fileList = fileList;
        }

        let _scanFile = (scanpath, indir) => {
            let list = fs.readdirSync(scanpath);
            list.forEach(file => {
                let fullpath = scanpath + "/" + file;
                
                if (fs.statSync(fullpath).isDirectory()) {
                    //.开头的目录忽略, 例如.svn, .idea等
                    if(/^\./.test(file)) {
                        return;
                    }
                    
                    dirList.push(file);
                    _scanFile(fullpath, true);
                    return;
                }
                
                let subfix = path.extname(file);
                if(this.allowSubfix.indexOf(subfix.toLowerCase()) !== -1) {
                    fileList.push({absolute:fullpath, name: dirList.join('/') + '/' + file});
                }
            });

            dirList.pop();
        };
        
        _scanFile(this.sourcePath);
        this.fileList = dirList.concat(fileList);
        return this.fileList;
    }
    
    groupFileList(fileList) {
        let newList = [];
        let dirMap = {};
        let index = 1;
        
        for(let i in fileList) {
            let item = fileList[i];
            let name = item.name;
            name = name.replace(/^[\\/]+/, '');
            let path = item.name;
            let ary = name.split('/');
            let filename = ary.pop();
            let ref = newList;
            
            if(ary.length > 0) {
                let curMapKeys = [];
                
                //有目录
                for(let j in ary) {
                    let dirname = ary[j];
                    curMapKeys.push(dirname);
                    let map = dirMap[curMapKeys.join('/')];
                    
                    if(map) {
                        ref = map.children;
                    } else {
                        let curDir = {
                            name: dirname,
                            isDir: true,
                            children: []
                        };
    
                        ref.push(curDir);
                        dirMap[curMapKeys.join('/')] = curDir;
                        ref = curDir.children;
                    }
                }
            }
    
            //移除后缀
            let newFileName = filename.replace(/\.[a-z0-9-]+$/i, '');
            if(!this.reserveNumPrefix) {
                newFileName = newFileName.replace(/^[0-9]+\./, '');
            }
            
            ref.push({
                absolute: item.absolute,
                name:newFileName,
                nameWithSubfix:filename,
                path,
                index: index++
            });
        }
        
        return newList;
    }
    
    renderCateDir(dir) {
        let name = dir.name;
        let html = `<li class="dir"><div onclick="toggle_dir_open(this)">${name}</div><ul>`

        for(let i in dir.children) {
            let childItem = dir.children[i];
            
            if(childItem.isDir) {
                html += this.renderCateDir(childItem);
            } else {
                html += this.renderCateFile(childItem);
            }
        }
        
        html += `</ul></li>`;
        return html;
    }
    
    renderCateFile(file) {
        return `<li class="file-catalog-item" target="mdcontent_${file.index}" path="${file.path}" onclick="active_doc(this)" title="${file.name}">${file.name}</li>`
    }
    
    renderContentDir(dir) {
        let html = '';
        
        for(let i in dir.children) {
            let childItem = dir.children[i];
        
            if(childItem.isDir) {
                html += this.renderContentDir(childItem);
            } else {
                html += this.renderContentFile(childItem);
            }
        }
        
        return html;
    }
    
    renderContentFile(file) {
        let mdContentTpl = new HtmlTpl(project_file_content('lib/tpl/frame-md-content.html'));
        let absolute = file.absolute;
    
        console.log('编译文件: ' + chalk.blueBright(file.absolute));
        //编译内容
        let currentMdContent = this.renderMdContentFromFile(absolute);
        let htmlParser = new JKHTMLParser();
        let domTree = htmlParser.parse(`<root>${currentMdContent}</root>`);
        let imgs = domTree.searchChildren({tags: ['img']});
    
        imgs.forEach(img => {
            let imgPath = img.getAttribute('src');
            if(!/^[a-z]+:\/\//i.test(imgPath)) {
                let imgRealPath = this.sourcePath + '/' + imgPath;
                
                if(!this.sourceIsDir) {
                    imgRealPath = path.dirname(this.sourcePath) + '/' + imgPath;
                }
            
                if(!fs.existsSync(imgRealPath)) {
                    console.warn(chalk.yellow('!警告: 图片 ' + imgPath + ' 不存在; path=' + imgRealPath));
                    return;
                }
            
                let absolutePath = fs.realpathSync(imgRealPath);
                let finish = (realpath) => {
                    img.setAttribute('base64-src', imgRealPath);
                };
            
                if(this.resources[absolutePath]) {
                    finish(absolutePath);
                    return;
                }
            
                let extname = path.extname(absolutePath);
                let mimeType = mime.getType(extname);
            
                if(!mimeType) {
                    console.log(chalk.yellow('!警告: 无法获取文件的 MIME 类型, 后缀:'+ extname + ', 文件路径: ' + imgPath +', 绝对路径: ' + absolutePath));
                    return;
                }
            
                let base64 = fs.readFileSync(absolutePath).toString('base64');
                this.resources[imgRealPath] = "data:" + mimeType + ";base64," + base64;
                console.log("打包文件: " + chalk.cyanBright(imgPath));
                finish(absolutePath);
            }
        });
    
        domTree.searchChildren({tags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']}).forEach(item => {
            if(item.toText().trim()) {
                item.appendChild(
                    new JKHTMLParser.Element(
                        JKHTMLParser.Element.TYPE.ELEMENT, 'i', {
                            attributes:{
                                class:{
                                    name:'class',
                                    originName:'class',
                                    value: 'link'
                                }
                            }
                    })
                )
            }
        });
    
        return mdContentTpl.compile({
            index: file.index,
            content: domTree.getInnerHTML()
        });
    }
    
    /**
     * 编译所有文件
     */
    compile() {
        let fileList = this.scanFile();
        this.reSort(fileList);
        fileList = this.groupFileList(fileList);
        let fileCatalogList = "";
        let fileCatalogTpl = new HtmlTpl(project_file_content('lib/tpl/frame-file-catalog.html'));
        let fileCatalogDirTpl = new HtmlTpl(project_file_content('lib/tpl/frame-file-catalog-dir.html'));
        let mdContent = "";
        let title = null;
        let dir="";
        let dirhtml="";
        
        let createCatalogDirHtml = (dir) => `<li class="dir"><div onclick="toggle_dir_open(this)">${dir}</div><ul>${dirhtml}</ul></li>`;
        
        for(let i in fileList) {
            let item = fileList[i];
            
            if(item.isDir) {
                fileCatalogList += this.renderCateDir(item);
                mdContent += this.renderContentDir(item);
            } else {
                if(!title) {
                    title = item.name;
                }
                
                fileCatalogList += this.renderCateFile(item);
                mdContent += this.renderContentFile(item);
            }
        }
        
        let cssContents = [], jsContents = [];

        jsContents.push("window.Params = " + JSON.stringify({
            resources: this.resources,
            sourceIsDir: this.sourceIsDir
        }));

        //流程图库
        if(hasMermaid) {
            // jsContents.push(project_file_content('lib/tpl/js/mermaid.js'));
            jsContents.push(project_file_content('lib/tpl/js/mermaid8.7.js'));
        }
        
        jsContents.push(project_file_content('lib/tpl/js/common.js'));

        //自定义js文件
        if(this.jsfile) {
            if(!fs.existsSync(this.jsfile)) {
                console.log(chalk.yellow("!警告: 找不到指定的[jsfile]文件 ("+ this.jsfile +")"));
            }

            jsContents.push(file_content(this.jsfile));
        }
        
        let uint8ary2str = ary => {
            let str = '';
            for(let i in ary) {
                str += String.fromCharCode(ary[i]);
            }
            
            return str;
        };
        // let result = sass.renderSync({data:project_file_content('lib/tpl/css/base.scss')});
        // cssContents.push(project_file_content('lib/tpl/css/base2.css'));
        
        cssContents.push(Buffer.from(
            sass.renderSync({data:project_file_content('lib/tpl/css/base.scss')}).css
            , 'gb2312').toString('utf8'));
    
        cssContents.push(Buffer.from(
            sass.renderSync({data:project_file_content('lib/tpl/css/hi.scss')}).css
            , 'gb2312').toString('utf8'));
        // cssContents.push(uint8ary2str(sass.renderSync({data:project_file_content('lib/tpl/css/hi.scss')}).css));
        // cssContents.push(project_file_content('lib/tpl/css/hi.css'));
        cssContents.push(this.getHiStyleContent());
        
        //自定义css文件
        if(this.cssfile) {
            if(!fs.existsSync(this.cssfile)) {
                console.log(chalk.yellow("!警告: 找不到指定的[cssfile]文件 ("+ this.jsfile +")"));
            }

            cssContents.push(file_content(this.cssfile));
        }

        fileCatalogList = `<ul>${fileCatalogList}</ul>`;
        let username = require('os').userInfo().username;
        let datef = require('./datef');
        let now = datef();
        
        return new HtmlTpl(
            project_file_content('lib/tpl/frame.html'), {
                title,
                fileCatalogList,
                mdContent,
                cssContent:  cssContents.join("\n"),
                jsContent: jsContents.join(";\n"),
                requireCatalog: this.sourceIsDir,
                buildVersion:`${now} ${username}`
            }
        ).compile();
    }

    /**
     * 开始构建
     * @returns {string}
     */
    build() {
       let saveFile = this.getSaveTarget();
       let content = this.compile();
       fs.writeFileSync(saveFile, content);
       return saveFile;
    }
    
    renderMdContent(str) {
        return markdown.render(str);
    }

    renderMdContentFromFile(file) {
        return this.renderMdContent(file_content(file));
    }
}
